package pusher;

import java.util.Arrays;

import structural.Constants;

/*
 * Classe Mère Grid basée sur la composition de trois (3) longs en tant que grille.
 * On utilise les fonctions de bit shift pour trouver les valeurs.
 * 
 * Remember (optimisation):
 * 
 * pos / 8 = (pos >> 3)
 * pos % 8 = (pos & 7)
 */

public class Grid {
	/*
	 * A B C D E F G H 8 || 00 01 02 03 04 05 06 07 | 7 || 08 09 10 11 12 13 14
	 * 15 | 6 || 16 17 18 19 20 21 22 23 | 5 || 24 25 26 27 28 29 30 31 | 4 ||
	 * 32 33 34 35 36 37 38 39 | 3 || 40 41 42 43 44 45 46 47 | 2 || 48 49 50 51
	 * 52 53 54 55 | 1 || 56 57 58 59 60 61 62 63 | |
	 * --------------------------- ---------------------------
	 */
	private long piecePosition = 0L;
	private long pieceColor = 0L;
	private long pieceType = 0L;
	private int lastMove; // 2 bytes needed iP[6] + fP[6] + pieceType[2] +
							// isKilledPiece[1] + killedPiece[2]

	public Grid() {
		piecePosition = 0xFFFF00000000FFFFL; // 0x7FFF08040007F3FL;
		pieceColor = 0xFFFF000000000000L; // 0x7FFF00000000000L; //
		pieceType = 0xFF000000000000FFL; // 0x7B060804000403FL; //
	}

	public Grid(String[] grid) {

		// Double t = Math.pow(2, 4);
		for (int i = 0; i < 64; i++) {

			int type = Integer.parseInt(grid[i]);

			if (type != 0) {
				if (type == Constants.BPUSHEE || type == Constants.BPUSHER) {
					this.pieceColor += (1L << (63 - i));
				}
				if (type == Constants.WPUSHER || type == Constants.BPUSHER) {
					this.pieceType += (1L << (63 - i));
				}
				this.piecePosition += (1L << (63 - i));
			}

		}
	}

	// Last Move played
	public int getLastMove() {
		return this.lastMove;
	}

	// return initial point of last Move
	public int getLastMoveInitialPoint() {
		return (((63 << 11) & this.lastMove)) >> 11;
	}

	public int getMoveInitialPoint(int move) {
		return (((63 << 11) & move)) >> 11;
	}

	// return final point of last Move
	public int getLastMoveFinalPoint() {
		return (((63 << 5) & this.lastMove)) >> 5;
	}

	public int getMoveFinalPoint(int move) {
		return (((63 << 5) & move)) >> 5;
	}

	// return moved piece type
	public int getLastMovePiece() {
		if (this.getLastMoveInitialPoint() == this.getLastMoveFinalPoint()) // une
																			// piece
																			// qui
																			// n
																			// 'a
																			// pas
																			// bougee
																			// retourne
																			// 0
			return Constants.EMPTY;

		return ((((3 << 3) & this.lastMove)) >> 3) + 1;
	}

	// return killed piece or empty if no killed Piece
	public int getLastMoveKilledPiece() {
		int type = ((1 << 2) & this.lastMove) >> 2;
		return (type == 0) ? Constants.EMPTY : (3 & lastMove) + 1;
	}	

	// return true if a case is empty in the grid
	// 1 = empty
	public boolean isEmpty(int pos) {
		if (pos < 0 || pos > 63)
			return true;
		else {
			long bitPos = (this.piecePosition & (1L << (63 - pos)));
			return !(bitPos > 0 || bitPos < 0);
		}
	}

	// return the color of a piece in the grid or empty constants
	// 1 = black
	public int getColor(int pos) {
		if (isEmpty(pos))
			return Constants.EMPTY;

		return ((this.pieceColor & (1L << (63 - pos))) == 0) ? Constants.WHITE
				: Constants.BLACK;
	}

	// return the type of a piece in the grid or empty constants
	// 1 = pusher
	public int getType(int pos) {
		if (isEmpty(pos))
			return Constants.EMPTY;

		return ((this.pieceType & (1L << (63 - pos))) == 0) ? Constants.PUSHEE
				: Constants.PUSHER;
	}

	// return the piece type corresponding to this position
	public int getPiece(int pos) {

		// protection qui pourrait être enlevée. Juste pour ne pas donner une
		// position inexistante
		if ((pos >> 3) > 7 || (pos & 7) > 7)
			return Constants.EMPTY;
		//----------------------------------------------------------------------
		// ---------------------

		switch (this.getType(pos)) {
		case Constants.PUSHER:
			return (this.getColor(pos) == Constants.BLACK) ? Constants.BPUSHER
					: Constants.WPUSHER;
		case Constants.PUSHEE:
			return (this.getColor(pos) == Constants.BLACK) ? Constants.BPUSHEE
					: Constants.WPUSHEE;
		default:
			return Constants.EMPTY;
		}
	}

	// return char value
	private char pieceToString(int pos) {
		switch (this.getPiece(pos)) {
		case Constants.BPUSHEE:
			return 'o';
		case Constants.WPUSHEE:
			return 'x';
		case Constants.BPUSHER:
			return 'O';
		case Constants.WPUSHER:
			return 'X';
		default:
			return '.';
		}
	}

	// display grid in console
	public void display() {
		for (int i = 0; i < 64; i++) {
			System.out.print(this.pieceToString(i));

			if (((i + 1) & 7) == 0)
				System.out.print("\n");

		}

		System.out.print("\n");
	}

	// return all the pieces position for the selected color in an array
	// use posConvert1D(row, column) method to obtain 1d value
	public int[] getPiecesByColor(int color) {
		int[] rtn = new int[16];
		Arrays.fill(rtn, -1); // null values. It cannot be Constants.EMPTY (0)
								// because position 0 really exists and may
								// contains something

		int c = 0;

		for (int i = 0; i < 64; i++) {

			long res = (color == Constants.BLACK) ? (this.piecePosition & this.pieceColor)
					& (1L << (63 - i))
					: (this.piecePosition ^ this.pieceColor) & (1L << (63 - i));

			if (res != 0) {
				rtn[c] = i;
				c++;
			}

		}

		return rtn;
	}

	public int getFirstPieceAfter(int color, int position) {
		int rtn = -1;

		for (int i = position + 1; i < 64; i++) {

			long res = (color == Constants.BLACK) ? (this.piecePosition & this.pieceColor)
					& (1L << (63 - i))
					: (this.piecePosition ^ this.pieceColor) & (1L << (63 - i));

			if (res != 0) {
				return i;
			}

		}

		return rtn;
	}

	public int getFirstPieceBefore(int color, int position) {
		int rtn = -1;

		for (int i = position - 1; i >= 0; i--) {

			long res = (color == Constants.BLACK) ? (this.piecePosition & this.pieceColor)
					& (1L << (63 - i))
					: (this.piecePosition ^ this.pieceColor) & (1L << (63 - i));

			if (res != 0) {
				return i;
			}

		}

		return rtn;
	}

	// return number of pieces for the selected type
	public int getPiecesCountType(int type) {
		int c = 0;

		for (int i = 0; i < 63; i++) {
			long res = (type == Constants.PUSHER) ? (this.piecePosition & this.pieceType)
					& (1L << (63 - i))
					: (this.piecePosition ^ this.pieceType) & (1L << (63 - i));

			if (res != 0)
				c++;
		}

		return c;
	}

	// return number of pieces for the selected color
	public int getPiecesCountColor(int color) {
		int c = 0;

		for (int p : this.getPiecesByColor(color)) {
			if (p != -1)
				c++;
		}

		return c;
	}

	// move a piece and keeping the move in the stack
	private void move(int iP, int fP) {
		int type = this.getPiece(iP);

		// remove old value by putting a 0 bit.
		if (this.getColor(iP) == Constants.BLACK) {
			this.pieceColor -= (1L << (63 - iP));
			this.pieceColor += (1L << (63 - fP));
		}

		if (this.getType(iP) == Constants.PUSHER) {
			this.pieceType -= (1L << (63 - iP));
			this.pieceType += (1L << (63 - fP));
		}

		this.piecePosition -= (1L << (63 - iP));
		this.piecePosition += (1L << (63 - fP));

		this.lastMove = (iP << 11) + (fP << 5) + ((type - 1) << 3);

	}

	// move a piece without keeping its trace
	private void moveWhitoutTrace(int iP, int fP) {

		// remove old value by putting a 0 bit.
		if (this.getColor(iP) == Constants.BLACK) {
			this.pieceColor -= (1L << (63 - iP));
			this.pieceColor += (1L << (63 - fP));
		}

		if (this.getType(iP) == Constants.PUSHER) {
			this.pieceType -= (1L << (63 - iP));
			this.pieceType += (1L << (63 - fP));
		}

		this.piecePosition -= (1L << (63 - iP));
		this.piecePosition += (1L << (63 - fP));

	}

	// kill enemy piece, move over it and keep the move
	private void killAndMove(int iP, int fP) {
		// iP = initial position
		// fP = enemy position

		int type = this.getPiece(iP);
		int killedType = this.getPiece(fP);

		// remove color if it's black
		if (this.getColor(fP) == Constants.BLACK)
			this.pieceColor -= (1L << (63 - fP));

		// remove pusher, if it's a pusher
		if (this.getType(fP) == Constants.PUSHER)
			this.pieceType -= (1L << (63 - fP));

		// clear enemy
		this.piecePosition -= (1L << (63 - fP));

		this.lastMove = (iP << 11) + (fP << 5) + ((type - 1) << 3) + (1 << 2)
				+ (killedType - 1);

		this.moveWhitoutTrace(iP, fP);

	}

	// Move Rules
	// ------------------------------------------------------------------

	// Moves

	public Boolean frontMove(int pos) {

		switch (this.getPiece(pos)) {
		case Constants.WPUSHER:
			if (this.isEmptyFront(pos)) {
				this.move(pos, pos - 8);

				return true;
			}
			break;

		case Constants.BPUSHER:
			if (this.isEmptyFront(pos)) {
				this.move(pos, pos + 8);
				return true;
			}
			break;

		case Constants.WPUSHEE:
			if (!this.isEmptyBack(pos)
					&& this.getPiece(pos + 8) == Constants.WPUSHER
					&& this.isEmptyFront(pos)) {
				this.move(pos, pos - 8);
				return true;
			}
			break;

		case Constants.BPUSHEE:
			if (!this.isEmptyBack(pos)
					&& this.getPiece(pos - 8) == Constants.BPUSHER
					&& this.isEmptyFront(pos)) {
				this.move(pos, pos + 8);
				return true;
			}
			break;
		}

		return false;
	}

	public Boolean leftDiagonalMove(int pos) {

		switch (this.getPiece(pos)) {
		case Constants.WPUSHER:
			if (!this.isLeftBorder(pos)) {
				if (this.isEmptyFrontLeftDiagonal(pos)) {
					this.move(pos, pos - 9);
					return true;
				} else if (this.isEnemyLeftDiagonal(pos)) {
					this.killAndMove(pos, pos - 9);
					return true;
				}
			}
			break;

		case Constants.BPUSHER:
			if (!this.isLeftBorder(pos)) {
				if (this.isEmptyFrontLeftDiagonal(pos)) {
					this.move(pos, pos + 7);
					return true;
				} else if (this.isEnemyLeftDiagonal(pos)) {
					this.killAndMove(pos, pos + 7);
					return true;
				}
			}
			break;

		case Constants.WPUSHEE:
			if (!this.isBorder(pos) && !isEmptyBackRightDiagonal(pos)
					&& this.getPiece(pos + 9) == Constants.WPUSHER) {
				if (this.isEmptyFrontLeftDiagonal(pos)) {
					this.move(pos, pos - 9);
					return true;
				} else if (this.isEnemyLeftDiagonal(pos)) {
					this.killAndMove(pos, pos - 9);
					return true;
				}
			}
			break;

		case Constants.BPUSHEE:
			if (!this.isBorder(pos) && !isEmptyBackRightDiagonal(pos)
					&& this.getPiece(pos - 7) == Constants.BPUSHER) {
				if (this.isEmptyFrontLeftDiagonal(pos)) {
					this.move(pos, pos + 7);
					return true;
				} else if (this.isEnemyLeftDiagonal(pos)) {
					this.killAndMove(pos, pos + 7);
					return true;
				}
			}
			break;
		}

		return false;
	}

	public Boolean rightDiagonalMove(int pos) {

		switch (this.getPiece(pos)) {
		case Constants.WPUSHER:
			if (!this.isRightBorder(pos)) {
				if (this.isEmptyFrontRightDiagonal(pos)) {
					this.move(pos, pos - 7);
					return true;
				} else if (this.isEnemyRightDiagonal(pos)) {
					this.killAndMove(pos, pos - 7);
					return true;
				}
			}
			break;

		case Constants.BPUSHER:
			if (!this.isRightBorder(pos)) {
				if (this.isEmptyFrontRightDiagonal(pos)) {
					this.move(pos, pos + 9);
					return true;
				} else if (this.isEnemyRightDiagonal(pos)) {
					this.killAndMove(pos, pos + 9);
					return true;
				}
			}
			break;

		case Constants.WPUSHEE:
			if (!this.isBorder(pos) && !isEmptyBackLeftDiagonal(pos)
					&& this.getPiece(pos + 7) == Constants.WPUSHER) {
				if (this.isEmptyFrontRightDiagonal(pos)) {
					this.move(pos, pos - 7);
					return true;
				} else if (this.isEnemyRightDiagonal(pos)) {
					this.killAndMove(pos, pos - 7);
					return true;
				}
			}
			break;

		case Constants.BPUSHEE:

			if (!this.isBorder(pos) && !isEmptyBackLeftDiagonal(pos)
					&& this.getPiece(pos - 9) == Constants.BPUSHER) {
				if (this.isEmptyFrontRightDiagonal(pos)) {
					this.move(pos, pos + 9);
					return true;
				} else if (this.isEnemyRightDiagonal(pos)) {
					this.killAndMove(pos, pos + 9);
					return true;
				}
			}
			break;
		}

		return false;
	}

	// border rules

	private Boolean isBorder(int pos) {
		return this.isTopBorder(pos) || this.isBottomBorder(pos)
				|| this.isLeftBorder(pos) || this.isRightBorder(pos);
	}

	private Boolean isLeftBorder(int pos) {
		return (pos & 7) == 0;
	}

	private Boolean isRightBorder(int pos) {
		return (pos & 7) == 7;
	}

	private Boolean isTopBorder(int pos) {
		return (pos >> 3) == 0;
	}

	private Boolean isBottomBorder(int pos) {
		return (pos >> 3) == 7;
	}

	// empty rules

	public Boolean isEmptyFront(int pos) {
		switch (this.getColor(pos)) {
		case Constants.BLACK:
			if (!isBottomBorder(pos))
				return isEmpty(pos + 8);
		case Constants.WHITE:
			if (!isTopBorder(pos))
				return isEmpty(pos - 8);

		default:
			return null;
		}
	}

	public Boolean isEmptyBack(int pos) {
		switch (this.getColor(pos)) {
		case Constants.BLACK:
			if (!isBottomBorder(pos))
				return isEmpty(pos - 8);
		case Constants.WHITE:
			if (!isTopBorder(pos))
				return isEmpty(pos + 8);

		default:
			return null;
		}
	}

	private Boolean isEmptyFrontLeftDiagonal(int pos) {

		switch (this.getColor(pos)) {
		case Constants.BLACK:
			if (!isBottomBorder(pos) && !isLeftBorder(pos))
				return isEmpty(pos + 7);
		case Constants.WHITE:
			if (!isTopBorder(pos) && !isLeftBorder(pos))
				return isEmpty(pos - 9);
		default:
			return null;
		}
	}

	public Boolean isEmptyBackLeftDiagonal(int pos) {

		switch (this.getColor(pos)) {
		case Constants.BLACK:
			if (!isBottomBorder(pos) && !isLeftBorder(pos))
				return isEmpty(pos - 9);
		case Constants.WHITE:
			if (!isTopBorder(pos) && !isLeftBorder(pos))
				return isEmpty(pos + 7);
		default:
			return null;
		}
	}

	private Boolean isEmptyFrontRightDiagonal(int pos) {

		switch (this.getColor(pos)) {
		case Constants.BLACK:
			if (!isBottomBorder(pos) && !isRightBorder(pos))
				return isEmpty(pos + 9);
		case Constants.WHITE:
			if (!isTopBorder(pos) && !isRightBorder(pos))
				return isEmpty(pos - 7);
		default:
			return null;
		}

	}

	private Boolean isEmptyBackRightDiagonal(int pos) {

		switch (this.getColor(pos)) {
		case Constants.BLACK:
			if (!isBottomBorder(pos) && !isRightBorder(pos))
				return isEmpty(pos - 7);
		case Constants.WHITE:
			if (!isTopBorder(pos) && !isRightBorder(pos))
				return isEmpty(pos + 9);
		default:
			return null;
		}
	}

	// Enemy rules

	private Boolean isEnemyRightDiagonal(int pos) {

		switch (this.getColor(pos)) {
		case Constants.BLACK:
			return !isEmptyFrontRightDiagonal(pos)
					&& this.getColor(pos + 9) == Constants.WHITE;
		case Constants.WHITE:
			return !isEmptyFrontRightDiagonal(pos)
					&& this.getColor(pos - 7) == Constants.BLACK;
		default:
			return null;
		}

	}

	private Boolean isEnemyLeftDiagonal(int pos) {

		switch (this.getColor(pos)) {
		case Constants.BLACK:
			return !isEmptyFrontLeftDiagonal(pos)
					&& this.getColor(pos + 7) == Constants.WHITE;
		case Constants.WHITE:
			return !isEmptyFrontLeftDiagonal(pos)
					&& this.getColor(pos - 9) == Constants.BLACK;
		default:
			return null;
		}

	}

	// copy with a new object reference

	@Override
	public Grid clone() {
		Grid grid = new Grid(); 
		grid.pieceColor = this.pieceColor;
		grid.piecePosition = this.piecePosition;
		grid.pieceType = this.pieceType;

		return grid;
	}

	public void copyGrid(Grid grid) {
		this.pieceColor = grid.pieceColor;
		this.piecePosition = grid.piecePosition;
		this.pieceType = grid.pieceType;
	}
}
